#!/usr/bin/python
# -*- coding: utf8 -*-

from pyparsing import Literal

from expresion import Expresion, Tipo, Exp, TipoExp, TypeException, makeSintaxis

from lenguaje import Extension

class TipoLista(Tipo):
    
    def __init__(self,toks):
        """
        Un tipo lista esta compuesto por el tipo de lista que sera.
        """
        self.tipo = toks[0]
   
    def __eq__(self,other):
        """
        Un tipo es igual a un tipo lista, si es una lista
        y se corresponden los tipos de lista que son.
        """
        return self.__class__ == other.__class__ and \
                    self.tipo == other.tipo 
    
    @classmethod
    def sintaxis(cls):
        Lista = makeSintaxis('Lista(', TipoExp ,')')
        return Lista
    
    def __str__(self):
        return 'Lista(%s)' % self.tipo
        

class Add(Expresion):
    
    
    def __init__(self,toks):
        """
        La expresion add esta compuesta por un elemento y una lista.
        """
        elem, list = toks
        self.elem = elem
        self.list = list
    
    def reducir(self):
        """
        El resultado de la reduccion de una expresion add, es otra expresion
        add con la lista y el elemente reducidos.
        """
        e = self.elem.reducir()
        l = self.list.reducir()
        return Add([e,l])
        
    def tipar(self,namespace):
        """
        gamma | > E : alpha, gamma | > L : Lista(alpha)
        ----------------------------------------------
        gamma | > add(E,L) : Lista(alpha)
        """
        te = self.elem.tipar(namespace)
        tl = self.list.tipar(namespace)
        if isinstance(tl, TipoLista):
            if te == tl.tipo:
                return TipoLista([te])
            else:
                raise TypeException('En la expresion %s elemento %s tiene tipo %s y no coincide con el tipo %s de la lista almacenadora.' % (self,self.elem,te,tl.tipo))
        else:
            raise TypeException('En la expresion %s el elemento %s tiene tipo %s y no constituye una lista.' % (self,self.list, tl))
        
    def sustituir(self, var, expresion):
        """
        add(E,L) {X <- T} --> add(E {X <- T}, L {X <- T})
        """
        e = self.elem.sustituir(var,expresion)
        l = self.list.sustituir(var,expresion)
        return Add([e,l])
        
    @classmethod
    def sintaxis(cls):
        Lista = makeSintaxis('add(', Exp, ', ',Exp ,')') 
        return Lista 
    
    def __str__(self):
        return "add(%s, %s)" % (self.elem, self.list)
       
    
class Vacia(Expresion):
    
    def __init__(self, toks):
        """
        La lista vacia esta identificada con un tipo, el de la lista.
        """
        self.tipo = toks[0]
    
    def reducir(self):
        """
        la lista vacia reduce a si misma.
        """
        return self
        
    def tipar(self,namespace):
        """
        vacio | > vacia(alpha) : Lista(alpha)
        """
        return TipoLista([self.tipo])
    
    def sustituir(self, var, expresion):
        """
        vacia(alpha) {X <- T} --> vacia(alpha)
        """
        return self
    
    @classmethod
    def sintaxis(cls):
        Vacia = makeSintaxis('vacia(', TipoExp, ')')
        return Vacia    
    
    def __str__(self):
        return "vacia(%s)" % str(self.tipo)
        
class CaseLista(Expresion):
    
    def __init__(self, toks):
        """
        Un case de lista esta compuesto por:
        - El tipo de lista del case.
        - La lista a observar.
        - El resultado por matching con la lista vacia.
        - El resultado por mathing con la lista de al menos un elemento.
        """
        tipo, list, resVacia, resAdd = toks
        self.tipo = tipo
        self.list = list
        self.resVacia = resVacia
        self.resAdd = resAdd
        
    def reducir(self):
        """
        En principio se reduce la lista a analizar, luego:
        - Si es vacia se reduce el resultado por matching con la lista vacia.
        - Si no:
            - caseLista(alpha) add(V,W); of vacia -> W; add(E,L) -> Z --> Z {E <- V,L <- W}
            - Luego se reduce Z {E <- V,L <- W}.
        """
        listR = self.list.reducir()
        if isinstance(listR,Vacia):
            return self.resVacia.reducir()
        else:
            sust1 = self.resAdd.sustituir('E', listR.elem)
            sust2 = sust1.sustituir('L', listR.list)
            return sust2.reducir()
        
    def tipar(self,namespace):
        """
        gamma | > M : Lista(alpha), gamma | > N : beta, gamma,{E:alpha,L:Lista(alpha)} | > O : beta
        -------------------------------------------------------------------------------------------
        gamma | > caseLista(alpha) M; of vacia -> N; add(E,L) -> O : beta
        """
        listt = self.list.tipar(namespace)
        trv = self.resVacia.tipar(namespace)
        name2 = dict(namespace)
        name2['E'] = self.tipo
        name2['L'] = TipoLista([self.tipo])
        tra = self.resAdd.tipar(name2)
        if listt == TipoLista([self.tipo]) and trv == tra:
            return tra
        else:
            raise TypeException(('Tipo Termino: %s \n'+ \
                                'Tipo Case vacia: %s \n'+ \
                                'Tipo Case add: %s \n'+ \
                                'El tipo de los resultados por matching Vacia o Add debe ser el mismo. \n' + \
                                'El tipo del Termino debe ser el mismo que el asociado a la clausula Case.') % (listt, trv, tra))
    
    def sustituir(self, var, expresion):
        """
        (caseLista(alpha) M; of vacia -> N; add(E,L) -> O) {X <- T} --> caseLista(alpha) A; of vacia -> B; add(E,L) -> C
        donde:
        - A = M {X <- T}
        - B = N {X <- T}
        - C = O {X <- T} si (X != L && X != E)
        - C = O si (X == L || X == E)
        """
        slist = self.list.sustituir(var, expresion)
        sresVacia = self.resVacia.sustituir(var, expresion)
        if (var != 'E') and (var != 'L'): 
            sresAdd = self.resAdd.sustituir(var, expresion)
        else:
            sresAdd = self.resAdd
        return CaseLista([self.tipo, slist, sresVacia, sresAdd])
    
    @classmethod
    def sintaxis(cls):
        Case = makeSintaxis('caseLista(',TipoExp,') ',Exp,'; of vacia -> ', Exp,'; add(E,L) -> ', Exp)
        return Case    
    
    def __str__(self):
        return 'caseLista(%s); %s of vacia -> %s; add(E,L) -> %s' % (self.tipo, self.list, self.resVacia, self.resAdd)


extensionLista = Extension("Listas", expresiones=[Vacia, Add, CaseLista], tipos=[TipoLista])
